﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Engines;
using Org.BouncyCastle.Crypto.Paddings;
using Org.BouncyCastle.Crypto.Parameters;

namespace LockCrypt.Core {
    public class LegacyEncryptionService : EncryptionBase {
        private byte[] _key;
        private byte[] _iv;

        /// <summary>
        /// Gets the encryption type.
        /// </summary>
        /// <value>The encryption type.</value>
        public override EncryptionProviders EncryptionAlgorithm {
            get { return EncryptionProviders.Aes256; }
        }

        /// <summary>
        /// Gets or sets the encryption key.
        /// </summary>
        /// <value>The encryption key.</value>
        public override string EncryptionKey {
            get { return _key == null ? null : Encoding.UTF8.GetString(_key); }
            set {
                _key = GetKeyBytes(value);
            }
        }

        /// <summary>
        /// Gets the initialization vector for encryption.
        /// </summary>
        /// <value>The initialization vector.</value>
        public byte[] InitializationVector {
            get { return _iv; }
        }

        /// <summary>
        /// Initializes a new <see cref="LegacyEncryptionService"/>.
        /// </summary>
        /// <param name="initializationVector">The initialization vector.</param>
        public LegacyEncryptionService(byte[] initializationVector)
            :base() {
            EncryptionKey = string.Empty;
            _iv = initializationVector;
        }
        /// <summary>
        /// Initializes a new <see cref="LegacyEncryptionService"/>.
        /// </summary>
        /// <param name="key">The encryption key.</param>
        /// <param name="initializationVector">The initialization vector.</param>
        public LegacyEncryptionService(string key, byte[] initializationVector)
            : this(initializationVector) {
            EncryptionKey = key;
        }

        /// <summary>
        /// Gets the equivalent key for a password, as a string.
        /// </summary>
        /// <param name="password">The password.</param>
        /// <returns>The string equivalent of the encryption key which would be used for the password.</returns>
        public override string GetKey(string password) {
            return password == null ? null : Encoding.UTF8.GetString(GetKeyBytes(password));
        }

        /// <summary>
        /// Gets the equivalent key for a password.
        /// </summary>
        /// <param name="password">The password.</param>
        /// <returns>The encryption key which would be used for the password.</returns>
        public override byte[] GetKeyBytes(string password) {
            MD5 md5 = new MD5CryptoServiceProvider();
            return md5.ComputeHash(Encoding.UTF8.GetBytes(password ?? string.Empty));
        }

        public override byte[] Encrypt(byte[] input) {
            using(RijndaelManaged rj = new RijndaelManaged()) {
                rj.Mode = CipherMode.CBC;
                rj.Padding = PaddingMode.PKCS7;
                rj.KeySize = 128;
                rj.BlockSize = 128;
                rj.IV = _iv;
                rj.Key = _key;
                ICryptoTransform encryptor = rj.CreateEncryptor();
                using(MemoryStream ms = new MemoryStream(input))
                using(CryptoStream cStream = new CryptoStream(ms, encryptor, CryptoStreamMode.Write))
                using(StreamReader reader = new StreamReader(cStream)) {
                    return Encoding.UTF8.GetBytes(reader.ReadToEnd());
                }
            }
        }

        /// <summary>
        /// Encrypts the specified stream.
        /// </summary>
        /// <param name="stream">The stream.</param>
        /// <returns>An encrypted stream.</returns>
        public CryptoStream Encrypt(Stream stream) {
            using(RijndaelManaged rj = new RijndaelManaged()) {
                rj.Mode = CipherMode.CBC;
                rj.Padding = PaddingMode.PKCS7;
                rj.KeySize = 128;
                rj.BlockSize = 128;
                rj.IV = _iv;
                rj.Key = _key;
                ICryptoTransform encryptor = rj.CreateEncryptor();
                return new CryptoStream(stream, encryptor, CryptoStreamMode.Write);
            }
        }

        public override byte[] Decrypt(byte[] input) {
            using(RijndaelManaged rj = new RijndaelManaged()) {
                rj.Mode = CipherMode.CBC;
                rj.Padding = PaddingMode.PKCS7;
                rj.KeySize = 128;
                rj.BlockSize = 128;
                rj.IV = _iv;
                rj.Key = _key;
                ICryptoTransform decryptor = rj.CreateDecryptor();
                using(MemoryStream ms = new MemoryStream(input))
                using(CryptoStream cStream = new CryptoStream(ms, decryptor, CryptoStreamMode.Read)) 
                using(StreamReader reader = new StreamReader(cStream)) {
                    return Encoding.UTF8.GetBytes(reader.ReadToEnd());
                }
            }
        }

        /// <summary>
        /// Decrypts the specified stream.
        /// </summary>
        /// <param name="stream">The stream.</param>
        /// <returns>An unencrypted stream.</returns>
        public CryptoStream Decrypt(Stream stream) {
            using(RijndaelManaged rj = new RijndaelManaged()) {
                rj.Mode = CipherMode.CBC;
                rj.Padding = PaddingMode.PKCS7;
                rj.KeySize = 128;
                rj.BlockSize = 128;
                rj.IV = _iv;
                rj.Key = _key;
                ICryptoTransform decryptor = rj.CreateDecryptor();
                return new CryptoStream(stream, decryptor, CryptoStreamMode.Read);
            }
        }
    }
}
